package midas.mw.crt;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

import midas.mw.cfg.CfgLogging;
import midas.mw.crt.common.CBMessage;

public class CBRCentralNodeClient implements Runnable{

	private static CbrImpl cbrInterface = null;
	private static final boolean debug = true; 
	Socket clientSocket = null;
	static int MAX_PACKET_LEN = 2000;
	int usedPort;
	String CentralNodeIp = null;
	boolean runCondition = true;
	QueueEmulate msgQueueRef;

	int timeoutCnt = 1;
	int timeoutWait = 60000;

	public CBRCentralNodeClient(CbrImpl cbrInp, String CentralNodeIp, int port, QueueEmulate msgQueue){
		cbrInterface = cbrInp;
		this.CentralNodeIp = CentralNodeIp;
		this.msgQueueRef = msgQueue;
		usedPort = port;
		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrCNClient", CfgLogging.SEV_DEBUG, "CN Client initialized successfully for server " + CentralNodeIp + "@" + usedPort);
	}

	public static CbrImpl getCbrHandle()
	{
		return cbrInterface;
	}

	public int sendMessageToServer(byte[] inpMsg){
		DataInputStream in = null;
		DataOutputStream out = null;
		try {
			InetAddress address = InetAddress.getByName(CentralNodeIp);
			clientSocket = new Socket(CentralNodeIp,usedPort);
			clientSocket.setSoTimeout(timeoutWait*timeoutCnt);
			byte[] buffer = new byte[MAX_PACKET_LEN];
			in = new DataInputStream( clientSocket.getInputStream());
			out = new DataOutputStream( clientSocket.getOutputStream());		
			
			
			/*byte[] bufferb = new byte[MAX_PACKET_LEN];
			for (int i=0;i<MAX_PACKET_LEN;i++) bufferb[i] = 0; 
			System.arraycopy(inpMsg, 0, bufferb, 0, inpMsg.length);
			System.out.println(new String (bufferb));
			
			out.write(bufferb);
			*/
			CBRCentralNodeClient.writePacket(inpMsg, out);
			
			
			CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrCNClient", CfgLogging.SEV_DEBUG, "Wait for response from server with timeout " + timeoutWait*timeoutCnt +" ms.");			
			//clientSocket.receive(response);
			/*int msglen = in.read(buffer, 0, MAX_PACKET_LEN);
		    byte[] req_b = new byte[msglen];
		    System.arraycopy(buffer, 0, req_b, 0, msglen);
		    */
			
			byte[] receivedMessage = CBRCentralNodeClient.readPacket(in);
			if (receivedMessage == null) {
					timeoutCnt++;
					in.close();
					out.close();
					clientSocket.close();
					return -1;
			}
			String firstResponse = new String(receivedMessage);
			if (debug){
				if (receivedMessage.length>0){
					CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrCNClient", CfgLogging.SEV_DEBUG, "Response from server received:" + firstResponse);
				}else{
					CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrCNClient", CfgLogging.SEV_DEBUG, "NULL response received from server !");
				}
			}

			int noOfMessagesToReceive=-1;
			if (firstResponse!=null){
				String[] splitResponse = firstResponse.split("=");
				if (splitResponse[1]!=null){
					noOfMessagesToReceive = new Integer(splitResponse[1].substring(0,splitResponse[1].indexOf("#"))).intValue();	
				}
			}

			if (debug){
				CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrCNClient", CfgLogging.SEV_DEBUG, "Receive " + noOfMessagesToReceive+" from CBR server now.");
			}

			for (int i=0;i<noOfMessagesToReceive;i++){
				/*int msglen = in.read(buffer, 0, MAX_PACKET_LEN);//clientSocket.receive(response);
				byte[] res_b = new byte[msglen];
				System.arraycopy(buffer, 0, res_b, 0, msglen);
				*/
				receivedMessage = CBRCentralNodeClient.readPacket(in);
				if (receivedMessage == null) {
					timeoutCnt++;
					in.close();
					out.close();
					clientSocket.close();
					return -1;
				}
				handleResponse(receivedMessage);	
				if (debug){
					CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrCNClient", CfgLogging.SEV_DEBUG, "Response from server received:" + new String(receivedMessage));
				}	
			}
			
			
			timeoutCnt = 1;
			in.close();
			out.close();
			clientSocket.close();
			return 0;
			
		} catch (SocketException e) {
			CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrCNClient", CfgLogging.SEV_ERROR, "sendMessageToCNServer(): SocketException " + e);
			//e.printStackTrace();
		} catch (UnknownHostException e) {
			CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrCNClient", CfgLogging.SEV_ERROR, "sendMessageToCNServer(): Can not find the central node" + e);
			//e.printStackTrace();
		} catch (SocketTimeoutException e) {
			CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrCNClient", CfgLogging.SEV_ERROR, "sendMessageToCNServer(): Socket timeout !" + e);
		} catch (IOException e) {
			CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrCNClient", CfgLogging.SEV_ERROR, "sendMessageToCNServer(): IOExcpetion" + e);
			//e.printStackTrace();
		}
		if (timeoutCnt<3){
			timeoutCnt = timeoutCnt + 1;
		}
		
		try {
			if (in!=null){
				in.close();
			}
		}catch (Exception e){}
		try{
			if (out!=null){
				out.close();
			}
		}catch (Exception e){}			
		try{
			if (clientSocket!=null){
				clientSocket.close();
			}
		}catch (Exception e){}
		
		return -1;
	}
	
	private void handleResponse(byte[] inp_b){
		CBMessage msg = new CBMessage("-2", inp_b, 0);
		String payload_str = new String(inp_b);

		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrCNClient", CfgLogging.SEV_DEBUG, "-> CBR message = " + msg);
		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrCNClient", CfgLogging.SEV_DEBUG, " payload: " + payload_str);

		((CbrImpl)getCbrHandle()).putCBMessage(msg);
	}
	
	public void stop(){
		runCondition = false;
		msgQueueRef.notify();
		if (clientSocket!=null){
			try {
				clientSocket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrCNClient", CfgLogging.SEV_INFORMAL, "stop(): Client thread stopped.");
	}

	
	public void run() {
		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrCNClient", CfgLogging.SEV_INFORMAL, "run(): Client thread started.");		
		byte[] msg;
		int result;
		try {
			while (runCondition){

				CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrCNClient", CfgLogging.SEV_INFORMAL, "Check message queue.");
				msg = (byte[]) msgQueueRef.dequeue();
				CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrCNClient", CfgLogging.SEV_INFORMAL, "Send message from queue."+ new String(msg));						
				result = sendMessageToServer(msg);
				if (result == -1){
					msgQueueRef.enqueue(msg);
					Thread.sleep(3000);
				}

			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public static int writePacket(byte[] msg, DataOutputStream out){
		int mlen = msg.length;
		
		byte[] bufferb = new byte[mlen+4];
		byte[] lengthInBytes = CBRCentralNodeClient.intToByte(mlen);
		
		for (int i=0;i<4;i++) 
			bufferb[i] = lengthInBytes[i];
		
		System.arraycopy(msg, 0, bufferb, 4, msg.length);
		//System.out.println("SEND DEBUG:"+new String (bufferb));
		try {
			out.write(bufferb);
		} catch (IOException e) {
			e.printStackTrace();
			return -1;
		}
		return 0;
	}
	
	public static byte[] readPacket(DataInputStream in){
		byte[] req_b;
		try {
			byte [] mlenB = new byte[4];
			in.readFully(mlenB, 0, 4);
			//System.out.println("MLEN:"+new String(mlenB));
			int mlen = CBRCentralNodeClient.byteToInt(mlenB);
			//System.out.println("MLEN:"+mlen);
			
			req_b = new byte[mlen];
			in.readFully(req_b, 0, mlen);
			
			//System.out.println("RECEIVED DEBUG:"+new String (req_b));
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
		
		return req_b;
	}
	
	private static byte[] intToByte(int value){
		return new byte[]{
                (byte)(value >>> 24),
                (byte)(value >>> 16),
                (byte)(value >>> 8),
                (byte)value
		};
	}
	
	private static int byteToInt(byte [] b){
        return (b[0] << 24) + ((b[1] & 0xFF) << 16) + ((b[2] & 0xFF) << 8) + (b[3] & 0xFF);
	}
}
